#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
#include <sys/time.h>
#include "matmul_aceMesh.h"
#include "aceMesh_runtime_c.h"
double A[_N/_TS][_M/_TS][_TS][_TS];
double B[_M/_TS][_P/_TS][_TS][_TS];
double C[_N/_TS][_P/_TS][_TS][_TS];
// double ****A,****B,****C;
extern void slave_stencil_core(targs *args);

void initialize(int N,int M,int P,int TS) 
{
   int i,j,ii,jj;
   for(i=0;i<N/TS;i++){
      for(j=0;j<M/TS;j++){
         for(ii=0;ii<TS;ii++){
            for(jj=0;jj<TS;jj++){
               A[i][j][ii][jj]=1.0;
            }
         }
      }
      for(j=0;j<P/TS;j++){
         for(ii=0;ii<TS;ii++){
            for(jj=0;jj<TS;jj++){
               C[i][j][ii][jj]=3.0;
            }
         }
      }
   }
   for(i=0;i<M/TS;i++){
      for(j=0;j<P/TS;j++){
         for(ii=0;ii<TS;ii++){
            for(jj=0;jj<TS;jj++){
               B[i][j][ii][jj]=2.0;
            }
         }
      }
   }
}


int matmul(int N, int M, int P, int TS) 
{
   int NB_I = N / TS;
   int NB_J = P / TS;
   int NB_K = M / TS;
   int i,j,k;
   targs each;
   acemesh_begin_split_task("dgemm_tile");
   for (k = 0; k < NB_K; k++) {
      for (i = 0; i < NB_I; i++) {
         for ( j = 0; j < NB_J; j++) {
            each.TS =TS;
            each.starti=i;
            each.startj=j;
            each.startk=k;
            acemesh_push_rlist(1,&A[i][k][0][0],NORMAL);
            acemesh_push_rlist(1,&B[k][j][0][0],NORMAL);
            acemesh_push_wrlist(1,&C[i][j][0][0],NORMAL);
            acemesh_task_generator((TASK_FUNCPTR)slave_stencil_core,(void*)(&each),sizeof(targs));
            acemesh_task_set_type(STENCIL_TASK);
         }
      }
   }
   acemesh_end_split_task();
   acemesh_spawn_and_wait(1);
   return 0;
}

bool verify(int N, int M, int TS, double value) 
{
   printf("begin check!\n");
   int NB_I = N / TS;
   int NB_J = M / TS;
   int i,j,ii,jj;
   bool validates = true;
   for ( i = 0; i < NB_I; i++) {
      for (j = 0; j < NB_J; j++) {
         for (ii = 0; ii < TS; ii++) {
            for ( jj = 0; jj < TS; jj++) {
               if (C[i][j][ii][jj] != value) {
                  validates = false;
                  break;
               }
            }
         }
      }
   }

   return validates;
}

int main(int argc, char* argv[]) 
{
   struct timeval start;
   struct timeval stop;
   double elapsed=0.0;

   // if (argc > 6) {
   //    fprintf(stderr, "input arg error,Usage: %s <N> <M> <P> <BLOCK_SIZE> <NUM_THREADS>\n", argv[0]);
   //    return 1;
   // }
   // int num_threads=8;
   // int N = 1024;
   // int M = 1024;
   // int P = 1024;
   // int TS = 128;
   // if(argc > 1)
   //    N = atoi(argv[1]);
   // if(argc > 2)
   //    M  = atoi(argv[2]);
   // if(argc > 3)
   //    P = atoi(argv[3]);
   // if(argc > 4)
   //    TS = atoi(argv[4]);
   // if(argc > 5)
   //    num_threads = atoi(argv[5]);
   if (_N%_TS != 0L) {
      printf("Error: TS (%d) must divide N (%d)\n", _TS, _N);
      return 1;
   }
   else if (_M%_TS != 0L) {
      printf("Error: TS (%d) must divide M (%d)\n", _TS, _M);
      return 1;
   }
   else if (_P%_TS != 0L) {
      printf("Error: TS (%d) must divide P (%d)\n", _TS, _P);
      return 1;
   }
   int num_threads=64;
   acemesh_runtime_init(num_threads);
   initialize(_N, _M, _P,_TS);
   if (A == NULL || B == NULL || C == NULL)
   {
      printf("Error: Can't allocate %ld Bytes\nExiting.\n", (_N*_M + _M*_P + _N*_P)*sizeof(double));
      exit(1);
   }
   gettimeofday(&start, NULL);
   matmul(_N, _M, _P, _TS);
   gettimeofday(&stop, NULL);
   acemesh_runtime_shutdown();

   elapsed += (stop.tv_sec - start.tv_sec)+(stop.tv_usec - start.tv_usec)/1000000.0;
   
#ifdef CHECK
   bool validates = verify(_N, _P, _TS, 3.0 + 2.0*1.0*_M);
   if (validates)
   { 
      printf("check is passed, correct!\n");
   }else{
      printf("Incorrect result obtained\nExiting.\n");
   }
#endif
   printf("N: %d, M: %d, P: %d, block size: %d, time (s): %lf\n", _N, _M, _P, _TS,elapsed);
   return 0;
}
